/*
 * Conversion of float to IEEE-754 and vice versa.
 *
 * © Copyright 2018 Pedro Gimeno Fortea.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <limits>

// Given an unsigned 32-bit integer representing an IEEE-754 single-precision
// float, return the float.
float i2f(uint32_t i)
{
  float res;
  int exp = (i >> 23) & 0xFF;
  uint32_t sign = i & 0x80000000UL;
  uint32_t imant = i & 0x7FFFFFUL;
  if (exp == 0xFF)
  {
    // Inf/NaN
    if (imant == 0)
    {
      if (std::numeric_limits<float>::has_infinity)
        return sign ? -std::numeric_limits<float>::infinity():
                       std::numeric_limits<float>::infinity();
      else
        return sign ? std::numeric_limits<float>::lowest() :
                      std::numeric_limits<float>::max();
    }
    return std::numeric_limits<float>::has_quiet_NaN ?
      std::numeric_limits<float>::quiet_NaN() : -0.f;
  }

  if (!exp)
    // Denormal or zero
    return sign ? -ldexpf((float)imant, -149) : ldexpf((float)imant, -149);

  return sign ?
   -ldexpf((float)(imant | 0x800000UL), exp - 150) :
    ldexpf((float)(imant | 0x800000UL), exp - 150);
}

// Given a float, return an unsigned 32-bit integer representing the float
// in IEEE-754 single-precision format.
uint32_t f2i(float f)
{
  uint32_t signbit = copysignf(1.0f, f) == 1.0f ? 0 : 0x80000000UL;
  uint32_t imant;
  int exp = 0; // set a value to silence warning
  if (f == 0.f)
    return signbit;
  if (isnanf(f))
    return signbit | 0x7FC00000UL;
  if (isinff(f))
    return signbit | 0x7F800000UL;
  float mant = frexpf(f, &exp);
  imant = (uint32_t)floorf((signbit ? -16777216.f : 16777216.f) * mant);
  exp += 126;
  if (exp <= 0)
    // Denormal
    return signbit | (exp <= -31 ? 0 : imant >> (1 - exp));

  if (exp >= 255)
    // Overflow due to the platform having exponents bigger than IEEE ones.
    // Return signed infinity.
    return signbit | 0x7F800000UL;

  // Regular number
  return signbit | (exp << 23) | (imant & 0x7FFFFFUL);
}

// Test the functions.
// This test needs the following requisites in order to work:
// - The compiler must support uint32_t.
// - The compiler must treat union by overlapping the memory of its fields.
// - The float type must be a 32 bits IEEE-754 single-precision float.
// - The endianness of floats and integers must match.
// - The compiler must allow access to union members without writing them (UB)
int main()
{
  union {
    float f;
    uint32_t i;
  } fui;

  float f;
  uint_fast32_t i;

  if (sizeof(fui) != 4)
  {
    puts("Floats are not 32 bits in this platform, aborting");
    return 1;
  }

  fui.i = 0;
  fui.f = -22220490.f;
  if (fui.i != 0xCBA98765UL)
  {
    puts("Invalid endianness or machine not IEEE-754 compliant, aborting");
    return 1;
  }

  // NaN checks aren't included in the main loop
  if (!isnanf(i2f(0x7FC00000UL)))
  {
    printf("i2f(0x7FC00000) failed to produce a NaN, actual: %.9g\n",
            i2f(0x7FC00000UL));
    return 1;
  }
  if (!isnanf(i2f(0xFFC00000UL)))
  {
    printf("i2f(0xFFC00000) failed to produce a NaN, actual: %.9g\n",
            i2f(0xFFC00000UL));
    return 1;
  }

  if (!std::numeric_limits<float>::has_quiet_NaN)
  {
    puts("No quiet NaN in std::numeric_limits, the NaN test will fail");
  }

  i = f2i(std::numeric_limits<float>::quiet_NaN());
  // check that it corresponds to a NaN encoding
  if ((i & 0x7F800000UL) != 0x7F800000UL || (i & 0x7FFFFFUL) == 0)
  {
    printf("f2i(NaN) failed to produce an encoding for NaN, actual: 0x%X\n", i);
    return 1;
  }

  puts("NaN test passed");

  for (i = 0; i <= 0xFF800000UL; i++)
  {
    if (i == 0x7F800001UL)
    {
      // Halfway there
      puts("Positive tests passed");
      i = 0x80000000UL;
    }

    fui.i = i;
    f = i2f(i);
    if (fui.f != f)
    {
      printf("i2f failed on 0x%X, expected %.9g, actual %.9g\n", i, fui.f, f);
      return 1;
    }
    if (f2i(f) != i)
    {
      printf("f2i failed on %.9g, expected 0x%X, actual 0x%X\n", f, i, f2i(f));
      return 1;
    }
  }

  puts("All tests passed");

  return 0;
}
